Skip to content

fix(server): fail start() when the pool's bootloader kills every worker - #143

Merged
EdmondDantes merged 2 commits into
mainfrom
fix/bootloader-failure-unreported
Aug 13, 2026
Merged

fix(server): fail start() when the pool's bootloader kills every worker#143
EdmondDantes merged 2 commits into
mainfrom
fix/bootloader-failure-unreported

Conversation

@EdmondDantes

@EdmondDantes EdmondDantes commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Reported by a user: a setBootloader() closure that throws produces no message anywhere — not on stdout, not on stderr, not in error_log — and the process exits 0.

Two causes, one in each repository.

The pool closes as soon as a bootloader throws, so every worker's submission is rejected before pool_worker_handler runs. pool_worker_done_cb discarded the rejection's exception ((void)exception) and rc was computed from pending == 0 alone, so start() answered true for a run in which nothing was ever bound and no connection was ever accepted. The callback now counts the rejected workers and prints the first reason:

[true-async-server] worker did not start: Exception: TEST BOOTLOADER ERROR

and start() answers false when that count is non-zero. A healthy run is unaffected: workers that serve and then stop complete without an exception, so the count stays zero and start() still answers true.

The exception text is the pool's half of the fix — true-async/php-async#225 — and reaches the user through the worker's own error stream (display_errors, error_log). This PR does not depend on it: 022-bootloader-failure.phpt asserts only what the parent owns, so it passes on either build, and #225 pins the report in its own repository.

Tests

022-bootloader-failure.phpt is new. tests/phpt/server/core locally: 78 tests, 8 failures, of which 7 fail identically on main without this change (verified by rebuilding the module without it and running the same eight); the eighth is 018-log-off-no-overhead, a timing test that fails only under the ten-worker parallel run and passes on its own.

A bootloader that throws closes the pool before any worker reaches its request
loop, so every submission is rejected and the server binds nothing. The parent
discarded the rejection's exception and read pending == 0 as success: start()
answered true for a run in which no connection was ever accepted, and the
process exited 0 with no trace of the cause.

pool_worker_done_cb now counts the rejected workers and prints the first
reason; start() answers false when that count is non-zero. The exception text
comes from the worker itself and needs the matching true_async build.
The fatal comes from the extension, so asserting it here made the test fail
on any build older than the pool fix. php-async pins that half already; this
test keeps what the server itself owns: the reason on stderr and start() ==
false.
@github-actions

Copy link
Copy Markdown
Contributor

Coverage

Total lines: 81.86% → 81.90% (+0.04 pp)

File Baseline Current Δ Touched
src/core/http_connection.c 75.26% 75.55% +0.29 pp
src/core/http_connection_tls.c 73.46% 73.74% +0.28 pp
src/http3/http3_callbacks.c 82.98% 83.65% +0.66 pp
src/http3/http3_listener.c 76.73% 76.88% +0.15 pp
src/http3/http3_static_response.c 76.37% 75.11% -1.27 pp
src/http_server_class.c 73.77% 73.87% +0.10 pp
src/http_server_config.c 92.45% 92.39% -0.06 pp
src/websocket/ws_session.c 89.94% 90.13% +0.19 pp

@EdmondDantes
EdmondDantes merged commit fe1a898 into main Aug 13, 2026
8 checks passed
@EdmondDantes
EdmondDantes deleted the fix/bootloader-failure-unreported branch August 13, 2026 10:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant